#include "TextMiningApp.hh"


bool compare (const t_match& item1,
	      const t_match& item2)
{
  if (item1.d > item2.d)
    return true;
  if (item1.d < item2.d)
    return false;
  if (item1.frequency < item2.frequency)
    return false;
  if (item1.frequency > item2.frequency)
    return true;
  if (item1.word.compare(item2.word) < 0)
    return true;
  return false;
}


void display_result(t_result_list& result, int d)
{
  result.sort(compare);

  t_result_list::const_iterator it = result.begin();

  std::cout << "[";

  for (;it != result.end();)
    {
      std::cout << "{\"word\":\"" << it->word
		<< "\",\"freq\":" << it->frequency
		<< ",\"distance\":" << d - it->d << "}";

      if (++it != result.end ())
	std::cout << ",";
    }

  std::cout << "]" << std::endl;
}


void closest_match(const OptimizedTrie* ot, const char* word, int d, t_data* data, std::string guess)
{
  data->nb_node_visited++;

  if ( d < 0 )
    {
      return;
    }
  else if(data->marks[ot - data->ot_head] && data->marks[ot - data->ot_head]->d > d)
    {
      return;
    }
  else if ( !word[0] )
    {

      guess += ot->value;
      // word found
      if ( ot->frequency > 0 )
	{
	  t_match* prev = data->marks[ot - data->ot_head];

	  if ( prev )
	    {
		prev->d = d;
	    }
	  else
	    {
	      t_match new_match = {guess.c_str () + 1, ot->frequency, d};
	      data->result.push_front(new_match);
	      data->marks[ot - data->ot_head] = &data->result.front();
	    }
	}

      // insertion at the end
      for (unsigned i = 0; i < ot->get_nb_sons(); ++i)
      	{
      	  closest_match(ot->ith_son(i), word, d - 1, data, guess);
      	}

    }
  else
    {

      for (unsigned i = 0; i < ot->get_nb_sons(); ++i)
	{
	  const OptimizedTrie* son = ot->ith_son(i);

	  if ( son->value == word[0] )
	    {
	      // normal case
	      closest_match(son, word + 1, d, data, guess + ot->value);
	    }
	  else
	    {

	      // Swap
	      if ( word[1] && son->value == word[1] )
		{

		  unsigned j = 0;
		  while ( j < son->get_nb_sons() && son->ith_son(j)->value < word[0])
		    {
		      ++j;
		    }

		  if ( j < son->get_nb_sons() && son->ith_son(j)->value == word[0])
		    {
		      closest_match(son->ith_son(j), word + 2, d - 1, data, guess + ot->value + son->value);
		    }
		}
	      // Substitution
	      closest_match(son, word + 1, d - 1, data, guess + ot->value);

	      // Insertion
	      closest_match(son, word, d - 1, data, guess + ot->value);
	    }
	}
      closest_match (ot, word + 1, d - 1, data, guess);
    }
}

int main(int argc, char* argv[])
{
  if (argc != 2)
    {
      std::cout << "usage: ./TextMiningApp /path/to/dict.bin" << std::endl;
      return 0;
    }

  int fd = open(argv[1], O_RDONLY);

  struct stat stat_buf_out;

  fstat(fd, &stat_buf_out);

  OptimizedTrie* ot =
    (OptimizedTrie*) mmap(NULL,
  			  stat_buf_out.st_size,
  			  PROT_READ, MAP_SHARED,
  			  fd, 0);

  if (ot == MAP_FAILED)
    {
      std::cerr << "MAP_FAILED" << std::endl;
      return 0;
    }

  unsigned ot_size = stat_buf_out.st_size / sizeof(OptimizedTrie);

  while ( std::cin.good() )
  {
    std::string command;
    std::string word;
    int d;

    std::cin >> command;
    std::cin >> d;
    std::cin >> word;

    if (command.compare("approx") == 0)
    {
      t_data data;
      data.marks = new t_match*[ot_size];
      memset(data.marks, 0, ot_size);

      data.ot_head = ot;
      data.nb_node_visited = 0;

      closest_match (ot, word.c_str(), d, &data);

      delete[] (data.marks);

      display_result(data.result, d);
    }
  }


  return 0;
}
